package cn.vehicle.config;

import cn.vehicle.utils.VehicleStringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Arrays;

/**
 * 控制层日志切面
 * <p>
 * 用于统一记录 Controller 层的请求信息、方法执行耗时与返回结果。
 * </p>
 * <p>功能说明：</p>
 * <ul>
 *   <li>拦截指定包下的所有 Controller 公共方法；</li>
 *   <li>打印请求 URL、方法名、参数与返回值；</li>
 *   <li>记录执行时间与异常信息；</li>
 *   <li>自动过滤不可序列化参数（如 MultipartFile、HttpServletRequest 等）。</li>
 * </ul>
 */
@Aspect
@Component
@Slf4j
public class ControllerLogAspect {

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 定义切点
     * <p>
     * 拦截 cn.vehicle.controller 包及其子包下的所有 public 方法。
     * </p>
     */
    @Pointcut("execution(public * cn.vehicle.controller..*.*(..))")
    public void controllerPointcut() {
    }

    /**
     * 环绕通知：记录 Controller 方法的执行日志
     *
     * @param joinPoint 连接点对象，包含目标方法、参数等信息
     * @return 目标方法的执行结果
     * @throws Throwable 如果目标方法抛出异常则继续向上抛出
     */
    @Around("controllerPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 记录方法开始执行时间，用于计算耗时
        long startTime = System.currentTimeMillis();

        // 获取当前请求的上下文信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = null;
        if (attributes != null) {
            request = attributes.getRequest();
        }

        // 获取目标类名与方法名
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();

        // 记录请求开始信息

        if (request != null) {
            log.info("=== Controller请求开始 ===");
            log.info("请求URL: {} {}", request.getMethod(), request.getRequestURI());
            log.info("执行方法: {}.{}", className, methodName);
            log.info("方法参数: {}", VehicleStringUtil.toNonBase64JsonString(formatArgs(args)));
        }

        // 执行目标方法并捕获结果或异常
        Object result;
        try {
            // 执行目标方法
            result = joinPoint.proceed();

            // 计算执行时间
            long executionTime = System.currentTimeMillis() - startTime;

            if (result != null) {
                // 记录响应信息
                log.info("方法返回: {}", formatResult(result));
                log.info("执行耗时: {}ms", executionTime);
                log.info("=== Controller请求结束 ===");
            }

            return result;

        } catch (Exception e) {
            // 记录异常信息与执行耗时
            long executionTime = System.currentTimeMillis() - startTime;
            log.error("方法执行异常: {}", e.getMessage());
            log.error("执行耗时: {}ms", executionTime);
            log.error("=== Controller请求异常结束 ===\n");
            throw e;
        }
    }

    /**
     * 将方法参数格式化为 JSON 字符串
     * <p>自动过滤不可序列化的参数类型。</p>
     *
     * @param args 方法参数数组
     * @return 格式化后的 JSON 字符串
     */
    private String formatArgs(Object[] args) {
        if (args == null || args.length == 0) {
            return "无参数";
        }

        try {
            // 使用流过滤掉 HttpServletRequest、MultipartFile 等不可序列化的对象
            Object[] filteredArgs = Arrays.stream(args)
                    .filter(arg -> arg != null && !isExcludedType(arg))
                    .toArray();

            if (filteredArgs.length == 0) {
                return "无有效参数";
            }

            return objectMapper.writeValueAsString(filteredArgs);
        } catch (Exception e) {
            return "参数序列化失败: " + Arrays.toString(args);
        }
    }

    /**
     * 将方法返回值格式化为 JSON 字符串
     *
     * @param result 方法返回值
     * @return JSON 字符串或序列化失败提示
     */
    private String formatResult(Object result) {
        if (result == null) {
            return "null";
        }

        try {
            return objectMapper.writeValueAsString(result);
        } catch (Exception e) {
            return "返回值序列化失败: " + result;
        }
    }

    /**
     * 判断参数是否属于需排除序列化的类型
     *
     * @param arg 参数对象
     * @return 若为排除类型则返回 true
     */
    private boolean isExcludedType(Object arg) {
        return arg instanceof HttpServletRequest
                || arg instanceof jakarta.servlet.http.HttpServletResponse
                || arg.getClass().getName().contains("MultipartFile");
    }
}
